package org.pwwong.fxscalautils.components.containers

import com.sun.scenario.scenegraph.fx.FXGroup

import org.pwwong.fxscalautils.Predef._
import org.pwwong.fxscalautils.components.Component
import org.pwwong.fxscalautils.components.layouthints._

import org.pwwong.scalautils.property.ListProp
import org.pwwong.scalautils.property.listevents._
import org.pwwong.scalautils.signals.Predef._
import org.pwwong.scalautils.signals.{Signal,BindableSignal}
import org.pwwong.scalautils.time.Predef._

import scala.collection.jcl
import scala.collection.mutable
import scala.concurrent.SyncVar

object LayoutManager {
  protected[containers] val dirtyContainerMap = new jcl.TreeMap[Int,mutable.Set[Container]](){
    override def compare(k0:Int,k1:Int) = -1*super.compare(k0,k1)
  }
  private var layoutLoop:Signal[Unit] = null
  def start() { 
    /*if(layoutLoop == null){
      println("start");
      layoutLoop = eval[Unit]{  
                         for( lcs <- dirtyContainerMap ){
                           dirtyContainerMap += (lcs._1 -> mutable.Set[Container]())
                           for( c <- lcs._2 ){ 
                             c.relayout
                             c.isDirty <= false
                           }
                         }
      } every(33 ms) 
    }*/
  }
  def requestReLayout(level:Int,c:Container) {
    if(!dirtyContainerMap.contains(level)) dirtyContainerMap += (level -> mutable.Set[Container]())
    dirtyContainerMap(level) += c 
  }
}

abstract class Container extends Component { self:Container =>
  type FXImpl = FXGroup
  compNodeImpl = new FXGroup
  
  protected[Container] var _containerLevel = -1
  
  //parent.onChange(this){ p:Option[Container] => _containerLevel =  if(p.isDefined) p.get._containerLevel +1 else -1 }
  protected[components] val isDirty = BindableSignal[Boolean](false)
  //isDirty.onChange(this){ dirty:Boolean => println("dirty change"); if(dirty && _containerLevel != -1) LayoutManager.requestReLayout(_containerLevel, this) }
  
  protected val _children = ListProp[Component]
  protected def setChildren(comps:Component*){ _children().clear; _children().insertAll(0,comps) }
  def children:Seq[Component] = _children()
  val onChildAdd = _children.onAdd
  val onChildRemove = _children.onRemove
  val onChildUpdate = _children.onUpdate
  val onChildrenClear = _children.onClear
  
  // Layout
  private var isLayingOut:Boolean = false
  final def relayout {
    println("relayout")
    if(isLayingOut == false){
      isLayingOut = true
      layout()
      isLayingOut = false
    }
  }
  protected def layout():Unit
  
  // Child bounds
  final protected def factorAnchorX(child:Component,newValue:Double):Double = (child.anchorX().origin match {
                                                                                 case LeftOrigin => 0.0
                                                                                 case RightOrigin => child.curWidth()
                                                                                 case CenterOrigin => child.curWidth()/2.0
                                                                              }) + 
                                                                              (child.anchorX().offset match {
                                                                                case PX(pxv) => pxv.toDouble
                                                                                case PCT(pctv) => pctv*child.curWidth()
                                                                              })
  
  final protected def factorAnchorY(child:Component,newValue:Double):Double = (child.anchorY().origin match {
                                                                                 case TopOrigin => 0.0
                                                                                 case BottomOrigin => child.curHeight()
                                                                                 case MiddleOrigin => child.curHeight()/2.0
                                                                              }) + 
                                                                              (child.anchorY().offset match {
                                                                                case PX(pxv) => pxv.toDouble
                                                                                case PCT(pctv) => pctv*child.curHeight()
                                                                              })
  
  final protected def setX(child:Component)(newValue:Double){ child._x <= newValue }
  final protected def setY(child:Component)(newValue:Double){ child._y <= newValue }
  final protected def setWidth(child:Component)(newValue:Double){ child._width <= newValue }
  final protected def setHeight(child:Component)(newValue:Double){ child._height <= newValue }
  final protected def setXY(child:Component,newX:Double, newY:Double){ child._x <= newX; child._y <= newY }
  final protected def setSize(child:Component,newW:Double, newH:Double){ child._width <= newW; child._height <= newH }
    
  protected def handleChildRemove(index:Int, remComp:Component){
    // Unset Parent
    remComp._parent <= None
    
    // Remove from FX backing
    compNodeImpl.remove(remComp.compNodeImpl)
    
    // Remove Reactions
    List(remComp.preferredWidth, remComp.preferredWidth,
         remComp.x, remComp.y,
         remComp.w, remComp.h).foreach(_ removeListener this)
    
    isDirty <= true
  } 
  protected def handleChildAdd(index:Int, newComp:Component){
    // Remove old parent (if exists)
    newComp.parent().foreach(oldParent => oldParent._children().remove(oldParent._children().indexOf(newComp)))
    
    // Add to FX backing
    compNodeImpl.add(index,newComp.compNodeImpl) 
            
    // Add Reactions
    onAnyChanges(newComp.preferredWidth, newComp.preferredHeight,
                 newComp.x, newComp.y,
                 newComp.w, newComp.h)(this)( isDirty <= true )
            
    // Set Parent (this)
    newComp._parent <= Some(this)
    
    isDirty <= true
  }  
  protected def handleChildrenCleared(){
    // Unset parent and Remove from FX backing
    while(compNodeImpl.getFXChildren.size>0) compNodeImpl.remove(0)
    
    isDirty <= true  
  }
  
  // Reactions
  {import _children._
	  onAdd(this){ adev:AddEvent[Component] => handleChildAdd(adev.index, adev.elem) }
	  onRemove(this){ remev:RemoveEvent[Component] => handleChildRemove(remev.index, remev.elem) }
      onClear(this){ clearev:ClearEvent[Component] => handleChildrenCleared() }
  }
  
  onAnyChanges(curWidth,curHeight)(this){ isDirty <= true }  // TODO fix clipping
}

